<template>
  <div class="container">
    <div class="app-container">
      <!-- default-expand-all	是否默认展开所有节点 default-expand-all 相当于 :default-expand-all='true'-->
      <!-- expand-on-click-node	是否在点击节点的时候展开或者收缩节点， 默认值为 true，如果为 false，则只有点箭头图标的时候才会展开或者收缩节点。 -->
      <el-tree
        :data="depts"
        :props="defaultProps"
        default-expand-all
        :expand-on-click-node="false"
      >
        <!-- 节点结构 -->
        <!-- 具名插槽 v-slot:插槽名 可以简化成 #插槽名 -->
        <!-- v-slot="{ node, data }" 只能作用在template -->
        <!-- 在template中, 通过  ` #插槽名= "obj" ` 接收，默认插槽名为 default -->
        <!--
          可以通过两种方法进行树节点内容的自定义：render-content和 scoped slot(作用域插槽)。
          使用render-content指定渲染函数，该函数返回需要的节点区内容即可。渲染函数的用法请参考 Vue 文档。
          使用 scoped slot 会传入两个参数node和data，分别表示当前节点的 Node 对象和当前节点的数据。
          注意：由于 jsfiddle 不支持 JSX 语法，所以render-content示例在 jsfiddle 中无法运行。
          但是在实际的项目中，只要正确地配置了相关依赖，就可以正常运行。
        -->
        <!--
          VUE作用域插槽 （＜slot＞、v-slot、slot-scope）
            插槽就是子组件中的提供给父组件使用的一个占位符，用slot标签 表示，
            父组件可以在这个占位符中填充任何模板代码，如 HTML、组件等，填充的内容会替换子组件的slot标签。
            简单理解就是子组件中留下个“坑”，父组件可以使用指定内容来补“坑”。
            插槽显不显示、怎样显示是由父组件来控制的，而插槽在哪里显示就由子组件来进行控制。

          具名插槽其实就是给插槽娶个名字,
          一个子组件可以放多个插槽，而且可以放在不同的地方，而父组件填充内容时，可以根据这个名字把内容填充到对应插槽中。
          子组件<slot name="name"></slot>
          父组件通过 v-slot:[name] 的方式指定到对应的插槽中,简写#name 注意：默认name 是default可以简写#default或v-slot
          总之，slot标签存在于子组件，v-slot在父组件，最终页面展示结果是父组件！！！

          默认插槽就是指没有名字的插槽，子组件未定义的名字的插槽，父级将会把 未指定插槽的填充的内容填充到默认插槽中。

          注意：
          1. 父级的填充内容如果指定到子组件的没有对应名字插槽，那么该内容不会被填充到默认插槽中。即具名插槽用name属性来表示插槽的名字，不传为默认插槽
          2. 如果子组件没有默认插槽，而父级的填充内容指定到默认插槽中，那么该内容就不会填充到子组件的任何一个插槽中。
          3. 如果子组件有多个默认插槽，而父组件所有指定到默认插槽的填充内容，将会且全都填充到子组件的每个默认插槽中。

          作用域插槽其实就是带数据的插槽，即带参数的插槽，简单的来说就是子组件提供给父组件的参数(子传父)，
          该参数仅限于插槽中使用，父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容。
          作用域插槽绑定了一套数据，父组件可以拿来用。于是，情况就变成了这样：样式父组件说了算，但内容可以显示子组件插槽绑定的。

          v-slot属性只能在template上使用，但在只有默认插槽时可以在组件标签上使用
          拓展用法：
          1、同样可以通过解构获取v-slot={user},还可以重命名v-slot="{user:newName}"和定义默认值v-slot="{user = '默认值'}"
          2、插槽名可以是动态变化的v-slot:[slotName]
          注意
          1.默认插槽名为default,可以省略default直接写v-slot,缩写为#时不能不写参数，写成#default(这点所有指令都一样，v-bind、v-on)
          2.多个插槽混用时，v-slot不能省略default

          在父组件中 vue2.5及以前使用
            slot属性弃用，具名插槽通过指令参数v-slot:插槽名的形式传入，可以简化为#插槽名
            slot-scope属性弃用，作用域插槽通过v-slot:xxx="slotProps"的slotProps来获取子组件传出的属性
            slot-scope属性弃用 在vue2.6中。这个 API被软废弃（3.0正式废弃），取而代之的是内置指令v-slot，可以缩写为【#】

            作用域插槽的原理
              slot本质上是返回VNode的函数，
              一般情况下，Vue中的组件要渲染到页面上需要经过template>>render function >> VNode >> DOM过程。
              组件挂载的本质就是执行渲染函数(render function)得到VNode，至于data/props/computed这些属性都是给VNode提供数据来源
              在2.5之前，如果是普通插槽就直接是VNode的形式了，而如果是作用域插槽，由于子组件需要传给父组件访问子组件的数据（子传父），所以父组件下是一个未执行的函数(slotScopre) => return h('div',slotScope.msg),接受子组件的slotProps参数，在子组件渲染实例时会调用该函数传入数据。
              在2.6之后，两者合并，普通插槽也变成一个函数，只是不接受参数了
            -->
        <!-- <template v-slot="{ node, data }"> -->
        <!-- <template v-slot="{ data }"> -->
        <template #default="{ data }">
          <!-- Layout 布局 通过基础的 24 分栏，迅速简便地创建布局 -->
          <!-- type	布局模式，可选 flex，现代浏览器下有效 -->
          <!--
            justify	flex 布局下的水平排列方式		start/end/center/space-around/space-between
            align	flex 布局下的垂直排列方式  top/middle/bottom
          -->
          <!-- 会按照数据条数循环展示 el-row -->
          <el-row
            style="width: 100%; height: 40px"
            type="flex"
            justify="space-between"
            align="middle"
          >
            <!-- span	栅格占据的列数	默认24 共24 分栏 下面两列 第二列占4 则第一列占剩下20 -->
            <el-col>
              <!-- <span>{{ node.label }}</span> -->
              <span>{{ data.name }}</span>
            </el-col>
            <el-col :span="4">
              <span class="tree-manager">{{ data.managerName }}</span>
              <!-- $event 实参 表示类型 -->
              <!-- command	点击菜单项触发的事件回调	dropdown-item 的指令 -->
              <!-- trigger	触发下拉的行为	hover, click -->
              <!-- :hide-on-click="false" hide-on-click	是否在点击菜单项后隐藏菜单 -->
              <!-- $event 实参 表示点击菜单传出来的类型 -->
              <!-- <el-dropdown trigger="click" @command="handleDept($event, data.id)"> -->
              <!-- 可以外层在包括一个函数  参数就是点击菜单传出来的类型 -->
              <el-dropdown trigger="click" @command="(type) => handleDept(type, data.id)">
                <!-- 显示区域内容 -->
                <span class="el-dropdown-link">
                  操作<i class="el-icon-arrow-down el-icon--right" />
                </span>
                <!-- 下拉菜单选项 -->
                <!-- dropdown	下拉列表，通常是 <el-dropdown-menu> 组件 -->
                <el-dropdown-menu slot="dropdown">
                  <!-- command	指令	string/number/object -->
                  <el-dropdown-item command="add">添加子部门</el-dropdown-item>
                  <el-dropdown-item command="edit">编辑部门</el-dropdown-item>
                  <el-dropdown-item command="del">删除</el-dropdown-item>
                </el-dropdown-menu>
              </el-dropdown>
            </el-col>
          </el-row>
        </template>
      </el-tree>
    </div>
    <!-- sync 修饰符与 $emit(update:xxx)  就不需要在此写监听事件了来修改了 -->
    <!-- 使用了sync修饰符，可以监听子组件传过来的 **update:属性名**的事件，直接将父组件的值进行修改 -->
    <!-- Rerender是子组件传的 要监听的变量，调用渲染函数getDepartment 重新渲染页面 -->
    <add-dept ref="deptComponent" :title="title" :select-id="selectId" :dialog-visible.sync="showDialog" @Rerender="getDepartment" />
  </div>
</template>

<script>
import { delDept, getDepartment } from '@/api/department'
import { arrayToTree } from '@/utils'
import addDept from './components/add-dept.vue'

export default {
  name: 'Department',
  components: {
    addDept
  },
  data() {
    return {
      title: '',
      selectId: null,
      showDialog: false,
      // data属性	展示数据
      depts: [],
      // props属性 配置选项
      // label	指定节点标签为节点对象的某个属性值	string, function(data, node)
      // children	指定子树为节点对象的某个属性值	string
      // disabled	指定节点选择框是否禁用为节点对象的某个属性值	boolean, function(data, node)
      // isLeaf	指定节点是否为叶子节点，仅在指定了 lazy 属性的情况下生效	boolean, function(data, node)
      defaultProps: {
        children: 'children',
        label: 'name'
      }
    }
  },
  created() {
    this.getDepartment()
  },
  methods: {
    async getDepartment() {
      const res = await getDepartment()

      // this.depts = transListToTreeData(res, 0)
      console.log(arrayToTree(res, 0))
      this.depts = arrayToTree(res, 0)
    },
    // el-dropdown @command事件触发  会把el-dropdown-item 里command="add"的属性值当参数传进函数
    async handleDept(type, id) {
      if (type === 'add') {
        this.showDialog = true
        this.selectId = id
        // 标题
        this.title = '新增部门'
      } else if (type === 'edit') {
        // 显示弹框
        this.showDialog = true
        // 标题
        this.title = '编辑部门'
        // 赋值id 传个子组件
        // 子组件props 接收
        // ‌Vue中的props本身不是异步的，但是可以通过异步方式传递数据。‌
        // 在Vue中，props的传递是同步的，即父组件将数据传递给子组件时，子组件会立即接收到这些数据。
        // 然而，如果父组件传递的是异步获取的数据，那么在数据实际到达之前，子组件可能无法立即接收到最新的数据。
        // 这是因为父组件的生命周期函数（如created或mounted）会在子组件的生命周期函数之前执行，导致子组件在异步数据到达时可能已经完成了初始化‌
        this.selectId = id
        // 调用子组件里的方法  通过ref $refs来获取子组件对象
        // this.$refs.deptComponent.getDeptInfoById(id)
        // 等上面异步执行完 执行$nextTick回调函数
        // 更新props- 异步动作
        // 直接调用了子组件的方法 同步的方法
        // 要在子组件获取数据
        // 父组件调用子组件的方法来获取数据
        // 用$nextTick，因为弹出层之后紧接着要调用子组件方法，第一次弹出层时，里面的组件还未完成渲染，
        // 所以要等到showDialog这个属性设置的数据完成渲染之后才进行调用
        this.$nextTick(() => {
          // this.$refs.addDept等同于子组件的this
          this.$refs.deptComponent.getDeptInfoById()
        })
      } else {
        this.$confirm('你确认要删除吗', '温馨提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
          // center: true
        }).then(async() => {
          await delDept(id)
          this.$message({
            type: 'success',
            message: '删除成功!'
          })
          // 重新渲染
          this.getDepartment()
        }).catch(() => {
          this.$message({
            type: 'info',
            message: '已取消删除'
          })
        })
      }
    }
  }
}
</script>

<style scoped lang="scss">
.app-container {
  padding: 20px 150px;
  font-size: 14px;

  .tree-manager {
    width: 100px;
    display: inline-block;
    margin: 10px;
  }
}
</style>
